﻿#region Using

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Vk.Methods.Common;
using Vk.Methods.Users;

#endregion

namespace Vk.Methods.Account
{
    /// <summary>
    ///     Действия с аккаунтом пользователя
    /// </summary>
    public class Account
    {
        private readonly Api _api;

        public Account(Api api)
        {
            _api = api;
        }

        /// <summary>
        ///     Добавляет пользователя в черный список
        /// </summary>
        /// <param name="userId">Идентификатор пользователя, которого нужно добавить в черный список</param>
        /// <returns>В случае успеха метод вернет true</returns>
        public async Task<bool> BanUser(int userId)
        {
            var query = new ApiQuery("account.banUser")
                .Add("user_id", userId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Позволяет сменить пароль пользователя после успешного восстановления доступа к аккаунту через СМС, используя метод
        ///     auth.restore.
        /// </summary>
        /// <param name="oldPassword">Текущий пароль пользователя. </param>
        /// <param name="newPassword">Новый пароль, который будет установлен в качестве текущего.</param>
        /// <param name="restoreSid">
        ///     Идентификатор сессии, полученный при восстановлении доступа используя метод auth.restore. (В
        ///     случае если пароль меняется сразу после восстановления доступа)
        /// </param>
        /// <param name="changePasswordHash">
        ///     Хэш, полученный при успешной OAuth авторизации по коду полученному по СМС (В случае если
        ///     пароль меняется сразу после восстановления доступа)
        /// </param>
        public async Task<ChangePasswordResult> ChangePassword(string oldPassword,
                                                               string newPassword,
                                                               string restoreSid = null,
                                                               string changePasswordHash = null)
        {
            if (oldPassword == null | newPassword == null)
            {
                throw new ArgumentNullException();
            }

            var query = new ApiQuery("account.changePassword")
                .Add("old_password", oldPassword)
                .Add("new_password", newPassword)
                .Add("restore_sid", restoreSid)
                .Add("change_password_hash", changePasswordHash);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new ChangePasswordResult(result) : null;
        }

        /// <summary>
        ///     Возвращает список активных рекламных предложений (офферов), выполнив которые пользователь сможет получить
        ///     соответствующее количество голосов на свой счёт внутри приложения.
        /// </summary>
        /// <param name="count">Количество офферов, которое необходимо получить </param>
        /// <param name="offset">Смещение, необходимое для выборки определенного подмножества офферов. </param>
        public async Task<ListCount<AdOffer>> GetActiveOffers(int? count = null, int? offset = null)
        {
            var query = new ApiQuery("account.getActiveOffers")
                .Add("offset", offset)
                .Add("count", count);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new ListCount<AdOffer>(result.Int("count").Value, result.SelectNodes("items/*").Select(x => new AdOffer(x)).ToList()) : null;
        }

        /// <summary>
        ///     Получает настройки текущего пользователя в данном приложении
        /// </summary>
        /// <param name="userId">
        ///     Идентификатор пользователя, информацию о настройках которого необходимо получить. По умолчанию —
        ///     текущий пользователь.
        /// </param>
        /// <returns>После успешного выполнения возвращает битовую маску настроек текущего пользователя в данном приложении</returns>
        public async Task<int> GetAppPermissions(int? userId = null)
        {
            var query = new ApiQuery("account.getAppPermissions")
                .Add("user_id", userId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Int().HasValue ? result.Int().Value : -1;
        }

        /// <summary>
        ///     Возвращает список пользователей, находящихся в черном списке
        /// </summary>
        /// <param name="count">Количество записей, которое необходимо вернуть</param>
        /// <param name="offset">Смещение необходимое для выборки определенного подмножества черного списка</param>
        public async Task<ListCount<BaseUser>> GetBanned(int? count = null, int? offset = null)
        {
            var query = new ApiQuery("account.getBanned")
                .Add("count", count)
                .Add("offset", offset);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                var nodes = result.SelectNodes("items/*");
                if (nodes != null && nodes.Any())
                    return new ListCount<BaseUser>(result.Int("count").Value,
                                                   nodes.Select(x => new BaseUser(x)).ToList());
            }
            return null;
        }

        /// <summary>
        ///     Возвращает ненулевые значения счетчиков пользователя.
        /// </summary>
        /// <param name="filter">
        ///     Счетчики, информацию о которых нужно вернуть (friends, messages, photos, videos, notes, gifts, events, groups,
        ///     notifications).
        ///     Список строк, разделенных через запятую
        /// </param>
        public async Task<AccountCounters> GetCounters(IEnumerable<string> filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException(nameof(filter));
            }

            var query = new ApiQuery("account.getCounters")
                .Add("filter", filter);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new AccountCounters(result) : null;
        }

        /// <summary>
        ///     Возвращает информацию о текущем аккаунте
        /// </summary>
        /// <param name="fields">Cписок полей, которые необходимо вернуть. </param>
        public async Task<AccountInfo> GetInfo(IEnumerable<string> fields = null)
        {
            var query = new ApiQuery("account.getInfo");

            //TODO: add possible values of fields
            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new AccountInfo(result) : null;
        }

        /// <summary>
        ///     Возвращает информацию о текущем профиле
        /// </summary>
        public async Task<ProfileInfo> GetProfileInfo()
        {
            var query = new ApiQuery("account.getProfileInfo");

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new ProfileInfo(result) : null;
        }

        /// <summary>
        ///     Позволяет искать пользователей ВКонтакте, используя телефонные номера, email-адреса, и идентификаторы пользователей
        ///     в других сервисах. Найденные пользователи могут быть также в дальнейшем получены методом friends.getSuggestions.
        /// </summary>
        /// <param name="contacts">Список контактов, разделенных через запятую </param>
        /// <param name="service">Строковой идентификатор сервиса, по контактам которого производится поиск</param>
        /// <param name="myContact">Контакт текущего пользователя в заданном сервисе</param>
        /// <param name="returnAll">
        ///     true – возвращать также контакты, найденные ранее используя этот сервис, false – возвращать
        ///     только контакты, найденные используя поле contacts.
        /// </param>
        /// <param name="fields">Список дополнительных полей, которые необходимо вернуть. </param>
        public async Task<List<ContactUser>> LookupContacts(IEnumerable<string> contacts,
                                                            ApiStringEnum<ContactService> service,
                                                            string myContact = null,
                                                            bool? returnAll = null,
                                                            IEnumerable<string> fields = null)
        {
            if (contacts == null)
                throw new ArgumentNullException(nameof(contacts));

            var query = new ApiQuery("account.lookupContacts")
                .Add("contacts", contacts)
                .Add("service", service)
                .Add("my_contact", myContact)
                .Add("fields", fields)
                .Add("return_all", returnAll);

            var result = await _api.Manager.Execute(query);
            if (_api.Manager.MethodSuccessed)
            {
                var nodes = result.SelectNodes("found/*");
                if (nodes != null && nodes.Any())
                    return nodes.Select(x => new ContactUser(x)).ToList();
            }
            return null;
        }

        /// <summary>
        ///     Позволяет редактировать информацию о текущем аккаунте
        /// </summary>
        /// <param name="name">Имя настройки </param>
        /// <param name="value">Значение настройки </param>
        /// <returns>В случае успеха метод вернет true</returns>
        public async Task<bool> SetInfo(string name, string value)
        {
            var query = new ApiQuery("account.setInfo")
                .Add("name", name)
                .Add("value", value);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Устанавливает короткое название приложения (до 17 символов), которое выводится пользователю в левом меню.
        ///     Это происходит только в том случае, если пользователь добавил приложение в левое меню со страницы приложения,
        ///     списка приложений или настроек.
        /// </summary>
        /// <param name="userId">Идентификатор пользователя. </param>
        /// <param name="name">Короткое название приложения</param>
        public async Task<bool> SetNameInMenu(string name, int? userId = null)
        {
            var query = new ApiQuery("account.setNameInMenu")
                .Add("user_id", userId)
                .Add("name", name);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Помечает текущего пользователя как offline
        /// </summary>
        /// <returns>В случае успешного выполнения метода будет возвращёно true. </returns>
        public async Task<bool> SetOffline()
        {
            var query = new ApiQuery("account.setOffline");

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Помечает текущего пользователя как online на 15 минут.
        /// </summary>
        /// <returns>В случае успешного выполнения метода будет возвращёно true. </returns>
        public async Task<bool> SetOnline(bool? voip = null)
        {
            var query = new ApiQuery("account.setOnline")
                .Add("voip", voip);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }

        /// <summary>
        ///     Редактирует информацию текущего профиля
        /// </summary>
        /// <param name="firstName">Имя пользователя</param>
        /// <param name="lastName">Фамилия пользователя</param>
        /// <param name="maidenName">Девичья фамилия пользователя</param>
        /// <param name="screenName">Короткое имя страницы. </param>
        /// <param name="cancelRequestId">
        ///     Идентификатор заявки на смену имени, которую необходимо отменить.
        ///     Если передан этот параметр, все остальные параметры игнорируются.
        /// </param>
        /// <param name="sex">Пол пользователя</param>
        /// <param name="relation">Семейное положение пользователя</param>
        /// <param name="relationPartnerId">Идентификатор пользователя, с которым связано семейное положение</param>
        /// <param name="bDate">Дата рождения пользователя в формате DD.MM.YYYY, например "15.11.1984"</param>
        /// <param name="bDateVisibility">
        ///     Видимость даты рождения. Возможные значения:
        ///     1 — показывать дату рождения;
        ///     2 — показывать только месяц и день;
        ///     0 — не показывать дату рождения
        /// </param>
        /// <param name="hometown">Родной город пользователя</param>
        /// <param name="countryId">Идентификатор страны пользователя</param>
        /// <param name="cityId">Идентификатор города пользователя</param>
        /// <param name="status">Статус пользователя, который также может быть изменен методом status.set </param>
        public async Task<SetProfileInfoResult> SaveProfileInfo(string firstName = null,
                                                               string lastName = null,
                                                               string maidenName = null,
                                                               string screenName = null,
                                                               int? cancelRequestId = null,
                                                               ApiEnum<UserSex> sex = null,
                                                               ApiEnum<UserMaritalStatus> relation = null,
                                                               int? relationPartnerId = null,
                                                               string bDate = null,
                                                               int? bDateVisibility = null, //TODO: create an enumeration
                                                               string hometown = null,
                                                               int? countryId = null,
                                                               int? cityId = null,
                                                               string status = null)
        {
            var query = new ApiQuery("account.saveProfileInfo")
                .Add("first_name", firstName)
                .Add("last_name", lastName)
                .Add("maiden_name", maidenName)
                .Add("screen_name", screenName)
                .Add("cancel_request_id", cancelRequestId)
                .Add("sex", sex)
                .Add("relation", relation)
                .Add("relation_partner_id", relationPartnerId)
                .Add("bdate", bDate)
                .Add("bdate_visibility", bDateVisibility)
                .Add("home_town", hometown)
                .Add("city_id", cityId)
                .Add("status", status)
                .Add("country_id", countryId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed ? new SetProfileInfoResult(result) : null;
        }

        /// <summary>
        ///     Убирает пользователя из черного списка
        /// </summary>
        /// <param name="userId">Идентификатор пользователя, которого нужно убрать из черного списка</param>
        /// <returns>В случае успеха метод вернет true</returns>
        public async Task<bool> UnbanUser(int userId)
        {
            var query = new ApiQuery("account.unbanUser")
                .Add("user_id", userId);

            var result = await _api.Manager.Execute(query);
            return _api.Manager.MethodSuccessed && result.Bool().Value;
        }
    }
}